/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package wf.plccom;

import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import wf.model.CounterData;
import wf.model.Schedule;

/**
 *
 * @author student
 */
public class PLCHighLevel {

    public static final int LOCK_POINTER = 4;
    public static final int COUNTER_STATUS_POINTER = 8;
    public static final int VALVE_STATUS_POINTER = 6;
    public static final int NUMBER_OF_COUNTERS = 4;
    public static final int NUMBER_OF_VALVES = 4;
    private static final int POINTER_INITIAL_ADDRESS = 139;
    private static final int POINTER_END_ADDRESS = 10860;
    private static final int COUNTER_1_UBOUND = 0x4000;
    private static final int COUNTER_2_UBOUND = 0x8000;
    private static final int COUNTER_3_UBOUND = 0xc000;
    private static final int[] STATUS_BITS = new int[]{
        -1, 1, 2, 4, 8};
    private PLCLowLevel core;

    public PLCHighLevel(String ip) {
        core = new PLCLowLevel(ip);
    }

    /**
     * return  the counter status = active or not active
     * @param cnt the counter number - counters = [1,2,3,4]
     * @return
     * @throws IOException
     */
    public int getDMPointer() throws IOException {
        return core.getDM(2)[0].toInteger();
    }

    public int getEEPPointer() throws IOException {
        return core.getEEP(5, 1)[0].toInteger();
    }

    public void unlockPLC() throws IOException {
        core.setEEP(4, 1, "0001");
    }

    public int getLockStatus() throws IOException {
        return getStatusIndication(LOCK_POINTER);
    }

    public PLCLowLevel getCore() {
        return core;
    }

    private int getStatusIndication(int address) throws IOException {
        Word[] value = core.getEEP(address, 1);
        System.out.println("STATUS INDICATION RETRIVED FROM ADDRESS " + address + ": " + value[0].toString());
        return value[0].toInteger();
    }

    public void setCounterStatus(int cnt, boolean status) throws IOException {
        System.out.println("Set Counter " + cnt + " to " + status);
        int v = getStatusIndication(COUNTER_STATUS_POINTER);
        System.out.println("Current Status: " + v);
        if (status == true) {
            v = (v | STATUS_BITS[cnt]);
        } else {
            v = (v & (~STATUS_BITS[cnt]));
        }
        System.out.println("New status: " + v);
        core.setEEP(COUNTER_STATUS_POINTER, 1, PLCLowLevel.pad(PLCLowLevel.toHex(v), 4));
        core.setCNT((10+cnt), 0);
    }

    public boolean getCounterStatus(int cnt) throws IOException {
        int v = getStatusIndication(COUNTER_STATUS_POINTER);

        if ((STATUS_BITS[cnt] & v) > 0) {
            return true;
        }
        return false;

    }

    /**
     * 
     * @return the counters statuses (counter 1 is on => ret[0] = true)
     * @throws IOException 
     */
    public boolean[] getCounterStatuses() throws IOException {
        int v = getStatusIndication(COUNTER_STATUS_POINTER);
        boolean[] ret = new boolean[NUMBER_OF_COUNTERS];
        for (int i = 1; i < ret.length; i++) {
            ret[i - 1] = (v & STATUS_BITS[i]) > 0;
        }
        return ret;
    }

    public void setValveStatus(int valveid, boolean status) throws IOException {
        System.out.println("Set Valve " + valveid + " to " + status);
        int v = getStatusIndication(VALVE_STATUS_POINTER);
        System.out.println("Current Status: " + v);
        if (status == true) {
            v = (v | STATUS_BITS[valveid]);
        } else {
            v = (v & (~STATUS_BITS[valveid]));
        }
        System.out.println("New status: " + v);
        core.setEEP(VALVE_STATUS_POINTER, 1, PLCLowLevel.pad(PLCLowLevel.toHex(v),4));
    }

    public boolean getValveStatus(int valveid) throws IOException {
        int v = getStatusIndication(VALVE_STATUS_POINTER);

        if ((STATUS_BITS[valveid] & v) > 0) {
            return true;
        }
        return false;
    }

    public boolean[] getValveStatuses() throws IOException {
        int v = getStatusIndication(VALVE_STATUS_POINTER);
        boolean[] ret = new boolean[NUMBER_OF_VALVES];
        for (int i = 1; i < ret.length; i++) {
            ret[i - 1] = (v & STATUS_BITS[i]) > 0;
        }
        return ret;
    }

    public void eraseCNTData() throws IOException {
        core.setDM(2, POINTER_INITIAL_ADDRESS);
//        for (int i = POINTER_INITIAL_ADDRESS; i < POINTER_END_ADDRESS; i++) {
//            core.setEEP(i, 1, "0000");
//        }
    }

    public void turnOnValve(int valveid) throws IOException {
        Word[] valves = core.getPLCSystemVariable( 2, 1 );
        int a = valves[0].toInteger();
        double b = Math.pow(2, valveid - 1);
        
        core.setPLCSystemVariable(2, 1, PLCLowLevel.toHex(((int) b) | a));
    }
    
    public void turnOffValve(int valveid) throws IOException {
        Word[] valves = core.getPLCSystemVariable( 2, 1 );
        int a = valves[0].toInteger();
        double b = Math.pow(2, valveid - 1);
        
        core.setPLCSystemVariable(2, 1, PLCLowLevel.toHex(~((int) b) & a));
    }
    
    public void setPLCDateTime() throws IOException {
        Date d = new Date();
        core.setPLCSystemVariable(9, 1, "" + PLCLowLevel.toHex(d.getYear() + 1900));
        core.setPLCSystemVariable(9, 2, "" + PLCLowLevel.toHex(d.getMonth() + 1));
        core.setPLCSystemVariable(9, 3, "" + PLCLowLevel.toHex(d.getDate()));
        core.setPLCSystemVariable(8, 1, "" + PLCLowLevel.toHex(d.getHours()));
        core.setPLCSystemVariable(8, 2, "" + PLCLowLevel.toHex(d.getMinutes()));
        core.setPLCSystemVariable(8, 3, "" + PLCLowLevel.toHex(d.getSeconds()));
    }

    public List<CounterData> readCounterData(int pointer) throws IOException {
        lockPLCMemory("cnt");
        //int pointer = getDMPointer();
        LinkedList<CounterData> ret = new LinkedList<CounterData>();
        Word[] wdate = core.getEEP(10, 4);

        System.out.println("Date Returned is " + wdate[0].toString() + wdate[1].toString() + wdate[2].toString() + wdate[3].toString());

        /**
         * first read all the records from the plc memory
         */
        for (int i = POINTER_INITIAL_ADDRESS + 1; i <= pointer; i++) { // 0x009A should be changed somehow to the value that returned from PLC.getCNT (maybe better to work with Integers).
            Word[] cur = core.getEEP(i, 1);
            final int cnum = counterNumFromData(cur[0]);
            ret.addFirst(
                    new CounterData(
                    -1,
                    cnum,
                    core.getIP(),
                    null,
                    extractCounterRead(cnum, cur[0])));
        }

        this.eraseCNTData();

        unLockPLCMemory("cnt");


        /**
         * now each record talking about different 10 minutes (record per counter)
         */
        Date date = parseCounterDataDate(wdate);

        /**
         * each counter has its own date and when reading counter data this date is reduced by 10 minutes so 
         * i will know that the date for the next counter record
         */
        Date[] dateByCounter = new Date[5];
        for (int i = 1; i <= NUMBER_OF_COUNTERS; i++) {
            dateByCounter[i] = new Date(date.getTime());
        }


        /**
         * calculating the date.
         */
        Iterator<CounterData> iter = ret.iterator();
        int n = 0;
        while (iter.hasNext()) {
            CounterData counter = iter.next();
            final Date curcounterDate = dateByCounter[counter.getCounterNumber()];
            counter.setDate(new Date(curcounterDate.getTime()));
            date.setTime(date.getTime() - 1000 * 60 * 10);
            curcounterDate.setTime(date.getTime());
        }

        return ret;
    }

    private int counterNumFromData(Word data) {
        int idata = data.toInteger();
        if (idata < COUNTER_1_UBOUND) {
            return 1;
        }
        if (idata < COUNTER_2_UBOUND) {
            return 2;
        }
        if (idata < COUNTER_3_UBOUND) {
            return 3;
        }
        return NUMBER_OF_COUNTERS;
    }

    public void addSchedulesToPLC(int valvePort,List<Schedule> ret) throws IOException {
        lockPLCMemory("sch");
 //       List<Schedule> ret = new LinkedList<Schedule>();
        int i = ret.size();

        int k, h=0;
        for (k = 0; k < i; k++) {
            if(ret.get(k).getDayTime().getTime() + ret.get(k).getPeriodMinute()*60*1000 > System.currentTimeMillis() ){
                int periodMinutes=ret.get(k).getPeriodMinute();
                if(System.currentTimeMillis() > ret.get(k).getDayTime().getTime()){
                    long timeToAdd = ret.get(k).getDayTime().getTime() + periodMinutes*60*1000 - System.currentTimeMillis();
                    if(timeToAdd > 0) periodMinutes = (int)(timeToAdd/60/1000);
                }
                if(periodMinutes > 256*256) periodMinutes=256*256;
                core.setEEP(20 + (valvePort-1) * 30 + h * 3, 3, 
                    core.pad("" + PLCLowLevel.toHex(ret.get(k).getDayTime().getMonth() + 1), 2) + 
                    core.pad("" + PLCLowLevel.toHex(ret.get(k).getDayTime().getDate()), 2) + 
                    core.pad("" + PLCLowLevel.toHex(ret.get(k).getDayTime().getHours()), 2) + 
                    core.pad("" + PLCLowLevel.toHex(ret.get(k).getDayTime().getMinutes()), 2) + 
                    core.pad("" + PLCLowLevel.toHex(periodMinutes), 4));
                h++;
            }
        }
        for (; h < 10; h++ ){
            core.setEEP(20 + (valvePort-1) * 30 + k * 3, 3, "000000000000");
        }
        unLockPLCMemory("sch");
    }

    private int extractCounterRead(int cnum, Word data) {
        final int i = data.toInteger();
        if (cnum == 1) {
            return i;
        }
        if (cnum == 2) {
            return i - COUNTER_1_UBOUND;
        }
        if (cnum == 3) {
            return i - COUNTER_2_UBOUND;
        }
        return i - COUNTER_3_UBOUND;
    }

    private Date parseCounterDataDate(Word[] wdate) {
        int day, month, year, min, hour, sec;

        final String mdstring = wdate[0].toString();
        final String hmstring = wdate[2].toString();

        month = Integer.parseInt(mdstring.substring(0, 2), 16);
        day = Integer.parseInt(mdstring.substring(2), 16);
        year = Integer.parseInt(wdate[1].toString(), 16);
        hour = Integer.parseInt(hmstring.substring(0, 2), 16);
        min = Integer.parseInt(hmstring.substring(2), 16);
        sec = Integer.parseInt(wdate[3].toString(), 16);

        Calendar cd = Calendar.getInstance();
        cd.set(year, month - 1, day, hour, min, sec);
        return cd.getTime();
    }


//    public void onStartPLC() throws IOException{
//        Word[] pointer = core.getEEP(5, 1);
//        if (!core.pad(pointer.toString(), NUMBER_OF_COUNTERS).equals("008C")){
//            addAllSchedulesToPLC();
//            core.setEEP(NUMBER_OF_COUNTERS, 1, "0001");
//        }
//    }
    /**
     *
     * @param lock can be cnt or scd
     * @throws IOException
     */
    public void lockPLCMemory(String lock) throws IOException {
        if (lock.equals("cnt")) {
            core.setCNT(23, 1);
            core.setCNT(25, 0);
            while (core.pad(core.getCNT(24).toString(), 1).equals("1") & core.pad(core.getCNT(25).toString(), 1).equals("0")) {
                try {
                    Thread.currentThread().sleep(100);
                } catch (InterruptedException ex) {
                    Logger.getLogger(PLCHighLevel.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        } else {
            core.setCNT(20, 1);
            core.setCNT(22, 0);
            while (core.pad(core.getCNT(21).toString(), 1).equals("1") & core.pad(core.getCNT(22).toString(), 1).equals("0")) {
                try {
                    Thread.currentThread().sleep(100);
                } catch (InterruptedException ex) {
                    Logger.getLogger(PLCHighLevel.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        }
//        core.setEEP(COUNTER_1_UBOUND, COUNTER_1_UBOUND, null)

    }

    public void unLockPLCMemory(String lock) throws IOException {
        if (lock.equals("cnt")) {
            core.setCNT(23, 0);
        } else {
            core.setCNT(20, 0);
        }
    }
}
